/**
 * @file   calculator.h
 * @author WinterMelonCDY <wintermeloncdy@wintermeloncdy-VirtualBox>
 * @date   Thu Nov 18 22:05:50 2021
 * 
 * @brief  
 * 
 * 
 */

#ifndef calculator_h
#define calculator_h

#include <iostream>
#include <string>
#include <cmath>
#include <stack>
#include <queue>
#include <set>
#include <algorithm>

class Calculator
{
private:
    struct element
    {
        int type;       // 0 stands for operator ， 1 stand for number
        char c;         // the operator character
        double num;     // double number
        bool is_op();   
        bool is_num();
        int priority()
        {
            switch (c)
            {
                case '+':
                    return 1;
                case '-':
                    return 2;
                case '*':
                    return 3;
                case '/':
                    return 3;
                case '(':
                    return -1;
                default:
                    return 0;
            }
        }
    };
    // The operator character
    const std::set<char> ValidOp = {'.','(',')','*','/','+','-'};
    const std::set<char> ValidNum = {'1','2','3','4','5','6','7','8','9','0'};
    std::stack<char> InputStack;
    std::queue<element> InfixQueue;
    std::queue<element> PostfixQueue;
    int message = 0; 
    // If message is not 0, output Error
    double result;
public:
    Calculator(std::string _str);
    void ResultPrint(int message);
    double char_to_double(char);
    bool is_num(char);
    bool is_op(char);
    bool is_valid(char);
    void test();
};

// Print out the result on the console
void Calculator::ResultPrint(int message)
{
    if (message == 0)
        std::cout << result << std::endl;
    else 
        std::cout << "Error." << std::endl;
}

// Check whether the character is an operator
bool Calculator::is_op(char _c)
{
    // Check whether the character is in the set of operator
    if(std::find(ValidOp.begin(), ValidOp.end(), _c) == ValidOp.end())
        return false;
    else
        return true;
}

// Return true if the character is number
bool Calculator::is_num(char _c)
{
    
    if(std::find (ValidNum.begin(), ValidNum.end(), _c) == ValidNum.end())
        return false;
    else
        return true;
}

// If the character is number or operator, return true
bool Calculator::is_valid(char _c)
{
    return (is_num(_c)||is_op(_c));
}

double Calculator::char_to_double(char c)
{
    double ans = (double)(int(c) - 48);
    return ans;
}

Calculator::Calculator(std::string _str)
{
    // only input the valid expression
    int n = (int)_str.length();
    for(int i = n-1 ; i >= 0 ; i-- )
    {
        char a = _str.at(i);
        if(is_valid(a))
        {
            InputStack.push(a);
        }
    }
    
    // Formatlize, convert string to queue of the operator character and double number
    int paraflag = 0;
    element zero;
    zero.type = 1 ;
    zero.num = 0.0;
    
    while (!InputStack.empty())
    {
        element ele;
        auto temp_a = InputStack.top();
        InputStack.pop();
        if (is_op(temp_a)&&temp_a!='.')
        {
            ele.type = 0;
            ele.c = temp_a;
            InfixQueue.push(ele);
            if(temp_a=='(')
                paraflag++;
            if(temp_a==')')
                paraflag--;
            if(paraflag < 0)
            {
                std::cout<<"Error."<<std::endl;
                return;
            }
            
        }
        else if(is_num(temp_a)) // Number loop
        {
            double num = char_to_double(temp_a);
            double dot = 10.0;
            int dotflag = 0;
            double time = 1.0;
            while (!InputStack.empty())
            {
                auto temp_b = InputStack.top();
                InputStack.pop();
                
                if (is_op(temp_b)&&temp_b!='.')
                {
                    InputStack.push(temp_b);
                    break;
                }
                else if(temp_b=='.')
                {
                    dot = 0.1;
                    dotflag++;
                    time = 1.0;
                }
                else if(is_num(temp_b))
                {
                    if(dotflag == 0)
                    {
                        num = num * dot + char_to_double(temp_b);
                    }
                    else
                    {
                        num += char_to_double(temp_b)*std::pow(dot,time);
                    }
                    time++;
                }
                
            }
            if (dotflag > 1)
            {
                std::cout << "Error." << std::endl;
                return;
            }
            ele.type = 1;
            ele.num = num;
            InfixQueue.push(ele);
        }
    }
    
    if(paraflag != 0)
    {
        std::cout<<"Error."<<std::endl;
        return;
    }

    // Transfer to postfix expression
    std::stack<element> container;// The temporary stack to store operator character
    while(!InfixQueue.empty())
    {
        element temp = InfixQueue.front();
        InfixQueue.pop();
        if (temp.type == 1)
        {
            PostfixQueue.push(temp);
        }
        else if( temp.type == 0 && temp.c != ')')
        {
            while(!container.empty())
            {
                auto pop = container.top();
                if(pop.priority() <= temp.priority()||temp.c=='(' )
                    break;
                PostfixQueue.push(pop);
                container.pop();
            }
            container.push(temp);
        }
        else if(temp.c == ')')
        {
            while(container.top().c!='(')
            {
                auto pop = container.top();
                //std::cout << pop.c  <<' '
                PostfixQueue.push(pop);
                container.pop();
            }
            container.pop();
        }
    }
    
    while(!container.empty())
    {
        auto pop = container.top();
        PostfixQueue.push(pop);
        container.pop();
    }

    // Calculate
    std::stack<element> CalculateStack;
    double lhs,rhs;
    element result;
    result.type = 1;
    while( !PostfixQueue.empty() )
    {
        auto temp = PostfixQueue.front();
        PostfixQueue.pop();
        if( temp.type == 1)
        {
            CalculateStack.push(temp);
        }
        else
        {
            switch (temp.c) {
                case '+':
                    rhs = CalculateStack.top().num;
                    CalculateStack.pop();
                    lhs = CalculateStack.top().num;
                    CalculateStack.pop();
                    result.num = lhs + rhs;
                    CalculateStack.push(result);
                    break;
                case '-':
                    rhs = CalculateStack.top().num;
                    CalculateStack.pop();
                    lhs = CalculateStack.top().num;
                    CalculateStack.pop();
                    result.num = lhs - rhs;
                    CalculateStack.push(result);
                    break;
                case '*':
                    rhs = CalculateStack.top().num;
                    CalculateStack.pop();
                    lhs = CalculateStack.top().num;
                    CalculateStack.pop();
                    result.num = lhs * rhs;
                    CalculateStack.push(result);
                    break;
                case '/':
                    rhs = CalculateStack.top().num;
                    CalculateStack.pop();
                    lhs = CalculateStack.top().num;
                    CalculateStack.pop();
                    result.num = lhs / rhs;
                    CalculateStack.push(result);
                    break;
                default:
                    break;
            }
        }     
    }

    std::cout << CalculateStack.top().num<< std::endl;
}

#endif /* calculator.h */

